package com.myk.game.gobangchess;

import com.myk.game.gobangchess.constants.*;
import com.myk.game.gobangchess.dates.ChatData;
import com.myk.game.gobangchess.signs.CacheSign;
import com.myk.game.gobangchess.utils.*;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.CopyOnWriteArrayList;


public abstract class Room {
    protected String roomName; //房间号
    private final String chessName; //棋的名字
    protected volatile List<Player> spectatorList; //观众列表
    private int playerMax; //房间准入人数上限
    private boolean isForbidChat; //是否禁止聊天
    private final boolean isSupportPersonal; //是否允许单人游玩支持
    private Mode mode; //棋的模式
    private String roomIntroduce; //房间简介 （不能输入 ”;“（用”；“替代，否则会出错，这里会自动替换） 和 ”&“）

    public Player player_black; //执黑的玩家
    public Player player_white; //执白的玩家
    protected int step = 0; //当前行棋的步数
    protected int cur = 0; //当前应该行棋的一方是谁，1黑方，2白方，3都能走，其它都不能走
    protected int blackScore = -1; //黑方分数 小于0客户端就不显示
    protected int whiteScore = -1; //白方分数 小于0客户端就不显示
    protected String gameTip = "-1"; //游戏状态提示文字（为空时显示 "-1"）
    public int[][] originGrids; //初始棋盘信息
    public int[][] curGrids; //当前棋盘信息  //0：空白，1：黑棋，2：白棋，3：红棋，4：蓝棋，5：黄棋，6：绿棋， 7：灰棋， 8：混合棋， 9：墙。
    public List<Sign> signList; //当前棋盘上的提示标记信息
    protected boolean isSaveHistoryToFile; //是否保存战报文件到本地
    protected History history; //历史记录
    protected int overFlag; //对弈结束状态，0：没有对弈，1：对弈进行中未结束，2：对弈结束等待双方确认，3：黑方想要继续等白方确认，4：白方想要继续等黑方确认
    protected long playingStartTime; //对弈开始时间
    protected Player leavePlayerRemember; //临时记住对弈中离开的玩家
    protected long leavePlayerLeaveTime; //临时记住对弈中离开的玩家的离开时间
    private String beforeLeaveGameTip = "-1"; //临时记录玩家逃跑前的提示文字，方便恢复后还原
    private final SortingWeighter sortingWeighter; //排序权重仪
    protected CountDownTimer blackCountdownOverTimer; //黑棋落子倒计时结束任务（自动被踢出座位）
    protected CountDownTimer whiteCountdownOverTimer; //白棋落子倒计时结束任务（自动被踢出座位）

    private volatile int chatCount = 0; //聊天消息计数器，用于玩家观察是否有消息记录未接收到

    public Room(String roomName, String roomIntroduce, String chessName, int[][] curGrids, boolean isSupportPersonal, boolean isSaveHistoryToFile, String... modes) {
        this.roomName = roomName;
        this.roomIntroduce = roomIntroduce.replace(";","；");
        this.chessName = chessName;
        this.originGrids = curGrids.clone();
        this.curGrids = GridsUtils.copyGrids(originGrids);
        this.signList = new ArrayList<>();
        this.playerMax = getPlayerMax();
        this.isForbidChat = false;
        this.isSupportPersonal = isSupportPersonal;
        this.spectatorList = new CopyOnWriteArrayList<>(); //使用线程安全容器保证线程安全
        this.isSaveHistoryToFile = isSaveHistoryToFile;
        this.mode = new Mode(modes);
        this.sortingWeighter = new SortingWeighter(this);
        this.onCreatePrepare();
    }

    //房间初创回调，管理员创建会多执行一次
    public void onCreatePrepare() {
    }

    //获取详细规则
    public abstract String getRuleText();

    //通知房间里所有人更新房间数据
    public void noticeAllRefreshRoomInfo() {
        String roomInfo = getRoomInfo();
        if (player_black != null) {
            player_black.sendMessage(Msger.refreshRoomInfo, roomInfo);
        }
        if (player_white != null) {
            player_white.sendMessage(Msger.refreshRoomInfo, roomInfo);
        }
        for (Player player : spectatorList) {
            if (player != null) {
                player.sendMessage(Msger.refreshRoomInfo, roomInfo);
            }
        }
    }

    //获取房间的数据
    public String getRoomInfo() {
        return "" + roomName +
                "&" + getBlackPlayerName() +
                "&" + getWhitePlayerName() +
                "&" + getRoomStatus() +
                "&" + getSpectatorNum() +
                "&" + getSpectatorNamesStr() +
                "&" + getPlayerNum() +
                "&" + playerMax +
                "&" + isForbidChat +
                "&" + "暂无"; //在房间里的暂时没有
    }

    //获取大厅里的房间数据
    public String getHallRoomInfo() {
        return "" + roomName +
                "&" + getBlackPlayerName() +
                "&" + getWhitePlayerName() +
                "&" + getRoomStatus() +
                "&" + getSpectatorNum() +
                "&" + getSpectatorNamesStr() +
                "&" + getPlayerNum() +
                "&" + playerMax +
                "&" + roomIntroduce +
                "&" + getSortingValue();
    }

    //获取游戏数据，当前棋盘的二维数组，当前该谁下棋，当前步数
    public String getGameInfo() {
        return "" + step +
                "&" + cur +
                "&" + blackScore +
                "&" + whiteScore +
                "&" + gameTip +
                "&" + GridsUtils.grids2Str(curGrids) +
                "&" + getSignsInfo() +
                "&" + getCoordMode();
    }

    //获取提示标记信息
    public String getSignsInfo() {
        return SignsUtils.signs2Str(signList);
    }

    /**
     * 获取缓存标记
     */
    public CacheSign getCacheSignByName(String cacheName) {
        return null;
    }

    /**
     * 获取客户端显示坐标的模式（只影响棋盘样式，不影响逻辑结算和战报记录）
     * 6种坐标类型：英文字母正序，英文字母倒序，阿拉伯数字正序，阿拉伯数字倒序，中文数字正序，中文数字倒序；
     * 分别对应数字1，2，3，4，5，6。  0表示不显示。
     * 前4位数字组合分别表示：左边，上边，右边，下边；
     * 例如：
     * "0000"  - 不显示坐标
     * "3100"  - 当前默认的效果，上边字母从左到右，左边数字从上到下
     * "0504"  - 中国象棋的标准坐标系，上边先手方中文数字从左到右，下边后手方阿拉伯数字从右到左
     * "4141"  - 国际象棋坐标系，上下是字母正序，左右是数字倒序
     * <p>
     * 后4位数字，说明坐标有缩进，那么后面4位数字分别表示：
     * 左右边起点缩进格数，左右边终点缩进格数，上下边起点缩进格数，上下边终点缩进格数。
     * 缩进后坐标总长度也会受到影响。
     * 例如：
     * "31001100" - 夺势棋左边数字坐标起点和终点都缩进1格当棋槽。
     */
    public String getCoordMode() {
        return "31000000";
    }

    /**
     * 获取这个房间里的玩家数量，包括游戏玩家和观众
     */
    public int getPlayerNum() {
        int num = 0;
        if (spectatorList != null) {
            num += spectatorList.size();
        }
        if (player_black != null) {
            num++;
        }
        if (player_white != null) {
            num++;
        }
        if (player_black != null && player_black == player_white){
            num--;
        }
        return num;
    }

    /**
     * 获取观众数量
     */
    public int getSpectatorNum() {
        return spectatorList.size();
    }

    /**
     * 获取这个房间的玩家数量上限
     */
    public int getPlayerMax() {
        return Integer.parseInt(AppConfig.room_max_player);
    }

    /**
     * 获取房间是否支持个人模式
     */
    public boolean isSupportPersonal() {
        return isSupportPersonal;
    }

    /**
     * 获取房间是否支持保存战报文件到本地
     */
    public boolean isSaveHistoryToFile() {
        return isSaveHistoryToFile;
    }

    /**
     * 获取房间名称
     */
    public String getRoomName() {
        return roomName;
    }

    /**
     * 获取棋名
     */
    public String getChessName() {
        return chessName;
    }

    /**
     * 获取该房间简介
     */
    public String getRoomIntroduce() {
        return roomIntroduce;
    }

    /**
     * 设置房间简介
     * （初始构造的时候已经有默认的简介了，如果传入的是”“或者“-1”，则会自动替换为默认简介）
     */
    public void setRoomIntroduce(String roomIntroduce) {
        if (roomIntroduce != null && !roomIntroduce.equals("") && !roomIntroduce.equals("-1")) {
            this.roomIntroduce = roomIntroduce.replace(";", "；");
        }
    }

    /**
     * 设置是否保存历史记录
     */
    public void setSaveHistoryToFile(boolean saveHistoryToFile) {
        isSaveHistoryToFile = saveHistoryToFile;
    }

    /**
     * 获取房间状态，0：没人上场， 1：黑等白 ，2：白等黑， 3：正在比赛, -1:不知道
     */
    public int getRoomStatus() {
        if (player_black == null && player_white == null) {
            return 0;
        }
        return -1;
    }

    /**
     * 获取当前行动中的玩家
     * (注意同时制的棋不适用该方法)
     */
    public Player getCurPlayer() {
        if (cur == 1) return player_black;
        if (cur == 2) return player_white;
        else return null;
    }

    /**
     * 获取黑色玩家的昵称
     */
    public String getBlackPlayerName() {
        if (player_black == null) {
            return "-1";
        }
        else {
            return player_black.getNickNameOrTemp();
        }
    }

    /**
     * 获取白色玩家的昵称
     */
    public String getWhitePlayerName() {
        if (player_white == null) {
            return "-1";
        }
        else {
            return player_white.getNickNameOrTemp();
        }
    }

    /**
     * 获取观众昵称清单
     */
    public String getSpectatorNamesStr() {
        StringBuilder sb = new StringBuilder();
        for (Player player : spectatorList) {
            if (player != null) {
                sb.append(player.getNickNameOrTemp()).append(",");
            }
        }
        String s = sb.toString();
        if (s.isEmpty()) {
            return "-1";
        }
        else {
            return s.substring(0, s.length() - 1);
        }
    }

    /**
     * step + 1
     */
    public void stepAdd() {
        step++;
    }

    /**
     * 设置游戏提示文本
     */
    public void setGameTip(String tip) {
        gameTip = tip;
    }

    /**
     * 获取落子等待倒计时，单位 秒
     */
    public int getCountdownSec() {
        if (player_black == player_white) return Integer.parseInt(AppConfig.player_countdown_sec_1side);
        else return Integer.parseInt(AppConfig.player_countdown_sec_2side);
    }


    /**
     * 玩家进入房间默认是观众
     */
    public void playerEnter(Player player) {
        removeSpectator(player);//先移出，避免因奇怪原因玩家多次进入，出现多个观众的问题
        addSpectator(player);
        player.setEnteringRoom(this);
        player.sendMessage(Msger.enterRoomSuccess, "Ok");
        player.sendMessage(Msger.refreshGameInfo, getGameInfo()); //刚进来更新一下游戏数据
        noticeAllRefreshRoomInfo();
        DataManager.noticeAllRefreshRoomList();
        noticePlayerCurrentCountDown(player); //通知刚进来的用户显示当前的倒计时动画
        System.out.println(player.getNickNameOrTemp() + "进入" + getRoomName());
    }

    /**
     * 玩家离开房间
     */
    public void playerLeaveRoom(Player player) {
        if (this.player_black == player && this.player_white == player) { //个人模式直接离开房间
            player_black = null;
            player_white = null;
            doOver(WinMode.BOTH_LEAVE, "个人玩家离开清空棋盘");
            noticeAllCountdown(-1, -1); //结束倒计时
            player.sendMessage(Msger.youNotMove, "Ok");
        }
        else { //非个人模式离开方
            if (this.player_black == player) {

                leavePlayerRemember = player_black; //记录对弈中离开的玩家对象
                leavePlayerLeaveTime = System.currentTimeMillis(); //记录离开时间
                player_black = null;
                if (player_white == null) {
                    doOver(WinMode.BOTH_LEAVE, "对弈双方离开清空棋盘");
                    leavePlayerRemember = null; //都离开和都落座时清空该对象
                    beforeLeaveGameTip = "-1";
                }
                else {
                    if (overFlag == 1) { //对弈中离开时
                        gameTip = "黑方" + leavePlayerRemember.getNickNameOrTemp() + "离开，可以等待新玩家上座继续对弈，" + "点击[结束]按钮将直接获胜。";
                        player_white.registerCustomBottom(new CustomBottom(this, "结束", player12 -> {
                            player12.clearCustomBottom();
                            player12.dispatchCustomBottom();
                            doOver(WinMode.BLACK_LEAVE, "黑方" + leavePlayerRemember.getNickNameOrTemp() + "离开，" + "白方" + player_white.getNickNameOrTemp() + "获胜");
                        }));
                        player_white.sendMessage(Msger.youNotMove, "Ok"); //对手离开，你也不能下棋了
                    }
                }
                noticeAllCountdown(-1, -1); //结束倒计时
            }
            else if (this.player_white == player) {
                leavePlayerRemember = player_white; //记录对弈中离开的玩家对象
                leavePlayerLeaveTime = System.currentTimeMillis(); //记录离开时间
                player_white = null;
                if (player_black == null) {
                    doOver(WinMode.BOTH_LEAVE, "对弈双方离开清空棋盘");
                    leavePlayerRemember = null; //都离开和都落座时清空该对象
                    beforeLeaveGameTip = "-1";
                }
                else {
                    if (overFlag == 1) { //对弈中离开时
                        gameTip = "白方" + leavePlayerRemember.getNickNameOrTemp() + "离开，可以等待新玩家上座继续对弈，" + "点击[结束]按钮将直接获胜。";
                        player_black.registerCustomBottom(new CustomBottom(this, "结束", player1 -> {
                            player1.clearCustomBottom();
                            player1.dispatchCustomBottom();
                            doOver(WinMode.WHITE_LEAVE, "白方" + leavePlayerRemember.getNickNameOrTemp() + "离开，" + "黑方" + player_black.getNickNameOrTemp() + "获胜");
                        }));
                        player_black.sendMessage(Msger.youNotMove, "Ok");
                    }
                }
                noticeAllCountdown(-1, -1); //结束倒计时
            }
            else {
                removeSpectator(player);
            }
        }

        removeSpectator(player);
        player.setEnteringRoom(null);
        if (player == player_black || player == player_white) {
            noticeAllRefreshGameInfo();   //观众离开房间不会影响局面不用刷新，在座玩家离开才会影响
        }
        noticeAllRefreshRoomInfo();
        DataManager.noticeAllRefreshRoomList();
        player.sendMessage(Msger.youNotMove, "Ok");

        System.out.println(player.getNickNameOrTemp() + "离开" + getRoomName());
    }

    /**
     * 添加到观众列表
     */
    public void addSpectator(Player player) {
        if (player != null) {
            spectatorList.add(player);
        }
    }

    /**
     * 从观众列表移除
     */
    public void removeSpectator(Player player) {
        if (player != null) {
            spectatorList.removeIf(player1 -> player1 == null || !player1.keepFlag || player1.getNickNameOrTemp().equals(player.getNickNameOrTemp()));
        }
    }

    /**
     * 玩家让座
     */
    public void playerLeaveSeat(Player player) {
        //让座
        if (this.player_black == player && this.player_white == player) { //个人模式的情况
            player_black = null;
            player_white = null;
            doOver(WinMode.BOTH_LEAVE, "个人玩家离开清空棋盘");
        }
        else { //双人对战时的情况
            if (this.player_black == player) {
                leavePlayerRemember = player_black;//记录对弈中离开的玩家对象
                leavePlayerLeaveTime = System.currentTimeMillis(); //记录离开时间
                player_black = null;
                if (player_white == null) { //如果玩家都走光，那么本对局清空
                    doOver(WinMode.BOTH_LEAVE, "对弈双方离开清空棋盘");
                    leavePlayerRemember = null; //都离开和都落座时清空该对象
                    beforeLeaveGameTip = "-1";
                }
                else {//座位只有一个人的时候不能再点继续
                    if (overFlag == 1) { //对弈中离开时
                        gameTip = "黑方" + leavePlayerRemember.getNickNameOrTemp() + "离开，可以等待新玩家上座继续对弈，" + "点击[结束]按钮将直接获胜。";
                        player_white.removeCustomBottom(player_white.findCustomBottomByTitle(this, "继续"));
                        player_white.registerCustomBottom(new CustomBottom(this, "结束", player1 -> {
                            player1.clearCustomBottom();
                            player1.dispatchCustomBottom();
                            doOver(WinMode.BLACK_LEAVE, "黑方" + leavePlayerRemember.getNickNameOrTemp() + "离开，" + "白方" + player_white.getNickNameOrTemp() + "获胜");
                        }));
                    }
                    else {
                        player_white.removeCustomBottom(player_white.findCustomBottomByTitle(this, "继续"));
                        player_white.dispatchCustomBottom();
                    }
                }
            }
            else if (this.player_white == player) {
                leavePlayerRemember = player_white;
                leavePlayerLeaveTime = System.currentTimeMillis();
                player_white = null;
                if (player_black == null) { //如果黑白都为空，那么对局清空
                    doOver(WinMode.BOTH_LEAVE, "对弈双方离开清空棋盘");
                    leavePlayerRemember = null; //都离开和都落座时清空该对象
                    beforeLeaveGameTip = "-1";
                }
                else { //座位只有一个人的时候不能再点继续
                    if (overFlag == 1) { //对弈中离开时
                        gameTip = "白方" + leavePlayerRemember.getNickNameOrTemp() + "离开，可以等待新玩家上座继续对弈，" + "点击[结束]按钮将直接获胜。";
                        player_black.removeCustomBottom(player_black.findCustomBottomByTitle(this, "继续"));
                        player_black.registerCustomBottom(new CustomBottom(this, "结束", player12 -> {
                            player12.clearCustomBottom();
                            player12.dispatchCustomBottom();
                            doOver(WinMode.WHITE_LEAVE, "白方" + leavePlayerRemember.getNickNameOrTemp() + "离开，" + "黑方" + player_black.getNickNameOrTemp() + "获胜");
                        }));
                    }
                    else {
                        player_black.removeCustomBottom(player_black.findCustomBottomByTitle(this, "继续"));
                        player_black.dispatchCustomBottom();
                    }
                }
            }
        }
        //成为观众
        removeSpectator(player); //先移出，避免因奇怪原因玩家多次进入，出现多个观众的问题
        addSpectator(player);
        //停止倒计时
        noticeAllCountdown(-1, -1);
        //取消触发棋盘事件的能力
        player.sendMessage(Msger.youNotMove, "Ok");
        //清空按钮
        player.clearCustomBottom();
        player.dispatchCustomBottom();
        //通知刷新
        noticeAllRefreshGameInfo();
        noticeAllRefreshRoomInfo();
        DataManager.noticeAllRefreshRoomList();
    }

    /**
     * 移除所有玩家
     */
    public void removeAllPlayer() {
        noticeAllCountdown(-1, -1);
        if (player_black != null) {
            player_black.sendMessage(Msger.youNotMove, "Ok");
            player_black.setEnteringRoom(null);
            player_black.sendMessage(Msger.loginSuccess, "Ok"); //借用登录成功的消息让客户端今日大厅页
            player_black = null;
        }
        if (player_white != null) {
            player_white.sendMessage(Msger.youNotMove, "Ok");
            player_white.setEnteringRoom(null);
            player_white.sendMessage(Msger.loginSuccess, "Ok"); //借用登录成功的消息让客户端今日大厅页
            player_white = null;
        }
        for (Player spectator : spectatorList) {
            if (spectator != null) {
                spectator.setEnteringRoom(null);
                spectator.sendMessage(Msger.loginSuccess, "Ok"); //借用登录成功的消息让客户端今日大厅页
            }
        }
        spectatorList.clear();
        leavePlayerRemember = null; //都离开和都落座时清空该对象
        beforeLeaveGameTip = "-1";
        doOver(WinMode.SYSTEM_CLEAN, "管理员清理房间清空棋盘");
        DataManager.noticeAllRefreshRoomList();
    }

    /**
     * 成为黑棋手
     */
    public void setBlackPlayer(Player player) {
        //已经是白方棋手的同时还想成为黑方棋手
        if (player == player_white) {
            if (overFlag == 1) {
                player.sendMessage(Msger.broadcast, "对弈中不能切换座位");
                return;
            }
            else {
                if (!isSupportPersonal) {
                    player_black = player;
                    player_white = null;//不支持个人模式的话就是换位置
                }
                else {
                    player_black = player;//支持个人模式的话 黑白都是player
                }
            }
        }
        else {
            //对弈中途离开，再次落座，要等待倒计时
            if (overFlag == 1 && player_white != null && leavePlayerRemember == player && System.currentTimeMillis() - leavePlayerLeaveTime < 5000){
                player.sendMessage(Msger.beChessPlayerFailed, "对弈时重新入座需要等待5s");
                return;
            }
            //从观众变成棋手
            removeSpectator(player);
            player_black = player;
        }

        player.sendMessage(Msger.beChessPlayerSuccess, "Ok");
        noticeAllRefreshRoomInfo();
        DataManager.noticeAllRefreshRoomList();

        if (overFlag == 1) { //对弈中状态
            if (player_white != null) {
                gameTip = beforeLeaveGameTip; //恢复旧提示
                beforeLeaveGameTip = "-1";
                player_white.removeCustomBottom(player_white.findCustomBottomByTitle(this, "结束"));
                player_white.dispatchCustomBottom();
                leavePlayerRemember = null; //都离开和都落座时清空该对象
            }
        }
        if (overFlag == 2 || overFlag == 3 || overFlag == 4) { //对弈结束状态成为棋手时，分发继续按钮
            overFlag = 2; //重进双方重新确认判断
            dispatchContinueButton();
        }

        if (overFlag == 0 || overFlag == 1) {
            checkGameStart();
        }
    }

    /**
     * 成为白棋手
     */
    public void setWhitePlayer(Player player) {
        //已经是黑方棋手同时还想成为白方棋手
        if (player == player_black) {
            if (overFlag == 1) {
                player.sendMessage(Msger.broadcast, "对弈中不能切换座位");
                return;
            }
            else {
                if (!isSupportPersonal) {
                    player_white = player;
                    player_black = null; //不支持个人模式的话就是换位置
                }
                else {
                    player_white = player; //支持个人模式的话 黑白都是player
                }
            }
        }
        //观众成为白方棋手
        else {
            //对弈中途离开，再次落座，要等待倒计时
            if (overFlag == 1 && player_black != null && leavePlayerRemember == player && System.currentTimeMillis() - leavePlayerLeaveTime < 5000){
                player.sendMessage(Msger.beChessPlayerFailed, "对弈时重新入座需要等待5s");
                return;
            }
            removeSpectator(player);//从观众变成棋手
            player_white = player;
        }

        player.sendMessage(Msger.beChessPlayerSuccess, "Ok");
        noticeAllRefreshRoomInfo();
        DataManager.noticeAllRefreshRoomList();

        if (overFlag == 1) { //对弈中状态
            if (player_black != null) { //黑白都在可以恢复对弈了
                gameTip = beforeLeaveGameTip; //恢复旧提示
                beforeLeaveGameTip = "-1";
                player_black.removeCustomBottom(player_black.findCustomBottomByTitle(this, "结束"));
                player_black.dispatchCustomBottom();
                leavePlayerRemember = null; //都离开和都落座时清空该对象
            }
        }
        if (overFlag == 2 || overFlag == 3 || overFlag == 4) { //对弈结束状态成为棋手时，分发继续按钮
            overFlag = 2; //重进双方重新确认判断
            dispatchContinueButton();
        }

        if (overFlag == 0 || overFlag == 1) {
            checkGameStart();
        }
    }

    /**
     * 用户重连方式进入房间
     */
    public void playerReConnectEnter(Player player) {
        player.sendMessage(Msger.enterRoomSuccess, "Ok"); //通知你进入房间成功
        player.sendMessage(Msger.refreshPlayerInfo, player.getPlayerInfo()); //更新角色
        player.sendMessage(Msger.refreshRoomInfo, getRoomInfo()); //更新一下游戏数据
        System.out.println(player.getNickNameOrTemp() + "重连到" + getRoomName());

        boolean isPlayingStarted = player_black != null && player_white != null; //黑白位置都有玩家，说明对弈是开始的状态
        boolean isOnSeatPlayer = player == player_black || player == player_white; //重连的玩家属于在座玩家
        if (isPlayingStarted && isOnSeatPlayer) { //checkGameStart()中满足要求了会刷新界面，用这种方式防止重复刷新

            /*重新分发可行动方（这里确定肯定已经doStart过了，不用重复调用）*/
            noticeOnSeatReConnectMove(player);

            /*处理对弈结束继续按钮分发的逻辑*/
            if (overFlag == 2 || overFlag == 3 || overFlag == 4) { //对弈结束状态成为棋手时，分发继续按钮
                overFlag = 2; //重进双方重新确认判断
                dispatchContinueButton();
            }
        }
        else {
            if (isOnSeatPlayer) {
                player.sendMessage(Msger.refreshGameInfo, getGameInfo()); //更新一下游戏数据
                player.dispatchCustomBottom();
            }
            else {
                player.sendMessage(Msger.refreshGameInfo, getGameInfo()); //更新一下游戏数据
            }
        }
    }

    /**
     * 分发在座重连方可移动
     */
    public void noticeOnSeatReConnectMove(Player player) {
        if (cur == 1) {
            player.sendMessage(Msger.youCanMove, "Ok");
        }
        else if (cur == 2) {
            player.sendMessage(Msger.youCanMove, "Ok");
        }
        else if (cur == 3) {
            player.sendMessage(Msger.youCanMove, "Ok");
        }
        else {
            player.sendMessage(Msger.youCanMove, "Ok");
        }
        player.sendMessage(Msger.refreshGameInfo, getGameInfo());
        player.dispatchCustomBottom();
        noticePlayerCurrentCountDown(player);
    }


    /**
     * 检查游戏是否开始等等
     */
    public void checkGameStart() {
        if (player_black != null && player_white != null) {
            if (step == 0) {
                doStart(); //对弈正式开始
            }
            else { //判断当前应该谁走并执行通知
                noticeCurMove();
            }
            //这里准备游戏内的按钮
            prepareInGameButtons();
        }
    }

    /**
     * 分发当前状态可移动方
     */
    public void noticeCurMove() {
        if (cur == 1) {
            noticeBlackMove();
        }
        else if (cur == 2) {
            noticeWhiteMove();
        }
        else if (cur == 3) {
            noticeAllMove();
        }
        else {
            noticeNoMove();
        }
    }

    /**
     * 执行对弈开始回调，基本上在step=0时才会触发， 黑方和白方都落座或者重开都会触发这里
     */
    protected void doStart() {
        DataManager.noticeAllGameStart(player_black, player_white, roomName);
        overFlag = 1;//重置结束状态：对弈进行中未结束
        playingStartTime = System.currentTimeMillis(); //重置对弈开始时间
        resetGrids();
        onStartPrepare(); //执行回调
        preHistory();
    }

    /**
     * 对弈开始，全局状态清理之后、通知界面刷新显示之前触发，可以用作定制棋对弈开始时的初始化事件
     */
    protected void onStartPrepare(){
        noticeBlackMove(); //默认通知黑方先行动
    }

    /**
     * 对弈开始 or 重新入座后、这个方法里根据全局状态分配游戏内的按钮，
     * 此方法肯定会在onStartPrepare()执行之后执行
     * 此方法肯定会在player_black、player_white不为空的时候执行
     */
    protected void prepareInGameButtons() {
    }

    /**
     * 复原棋盘（房间子类所有全局变量都改在此重置）
     */
    public void resetGrids() {
        step = 0;
        gameTip = "-1";
        curGrids = GridsUtils.copyGrids(originGrids);
        signList.clear();
    }

    /**
     * 通知黑方行棋
     */
    public void noticeBlackMove() {
        cur = 1;
        if (player_white != null) {
            player_white.sendMessage(Msger.youNotMove, "Ok");
        }
        if (player_black != null) {
            player_black.sendMessage(Msger.youCanMove, "Ok");
        }
        noticeAllRefreshGameInfo();
        noticeAllCountdown(getCountdownSec(), -1);
    }

    /**
     * 通知白方行棋
     */
    public void noticeWhiteMove() {
        cur = 2;
        if (player_black != null) {
            player_black.sendMessage(Msger.youNotMove, "Ok");
        }
        if (player_white != null) {
            player_white.sendMessage(Msger.youCanMove, "Ok");
        }
        noticeAllRefreshGameInfo();
        noticeAllCountdown(-1, getCountdownSec());
    }

    /**
     * 通知双方都能行棋
     */
    public void noticeAllMove() {
        cur = 3; //客户端黑白双方都会显示绿框
        if (player_black != null) {
            player_black.sendMessage(Msger.youCanMove, "Ok"); //客户端能够上送落子
        }
        if (player_white != null) {
            player_white.sendMessage(Msger.youCanMove, "Ok");
        }
        noticeAllRefreshGameInfo();
        noticeAllCountdown(getCountdownSec(), getCountdownSec());
    }

    /**
     * 通知双方都不能行棋
     */
    public void noticeNoMove() {
        cur = 0; //客户端黑白双方都不显示绿框
        if (player_black != null) {
            player_black.sendMessage(Msger.youNotMove, "Ok");
        }
        if (player_white != null) {
            player_white.sendMessage(Msger.youNotMove, "Ok");
        }
        noticeAllRefreshGameInfo();
        noticeAllCountdown(-1, -1);
    }

    /**
     * 下棋方法,先判断下棋人，然后交给落子规则
     */
    public void moveLater(Player player, int row, int col) {
        //个人模式下
        if (player_black != null && player_white != null && player_black == player_white) {
            if (cur == 1) {
                doRule(row, col, 1);
            }
            else if (cur == 2) {
                doRule(row, col, 2);
            }
        }
        //双人对弈模式下
        else {
            if (player_black == player) {
                if (cur == 1) {
                    doRule(row, col, 1);
                }
            }
            if (player_white == player) {
                if (cur == 2) {
                    doRule(row, col, 2);
                }
            }
        }
    }

    //更具落子结算规则
    public abstract void doRule(int row, int col, int value);

    /**
     * 认输
     */
    public void admitDefeat(Player player) {
        if (player_black == player && player_white == player) {
            doOver(WinMode.PERSONAL_RESTART, "个人模式认输重开"); //个人模式认数
        }
        else {
            if (overFlag == 1) { //只有对弈中状态才有认输
                if (player_black == player) {
                    doOver(WinMode.BLACK_ADMIT, "黑方-" + getBlackPlayerName() + "认输"); //黑方认输
                }
                if (player_white == player) {
                    doOver(WinMode.WHITE_ADMIT, "白方-" + getWhitePlayerName() + "认输"); //白方认输
                }
            }
        }
    }

    /**
     * 执行对弈结束回调
     */
    public void doOver(int winMode, String winMsg) {
        savePlayingInfo(winMode); //记录对弈信息
        saveHistory(winMode); //不同模式存储历史记录

        overFlag = 2; //重置结束状态：对弈结束等待双方确认
        if (winMode == WinMode.BOTH_DRAW) { //双方战平
            noticeAllWinMsg(winMsg); //通知房间内所有人
            noticeNoMove(); //通知双方都不能再行动了
            dispatchContinueButton(); //分配重开逻辑
        }
        else if (winMode == WinMode.BLACK_WIN) { //黑方下赢
            noticeAllWinMsg(winMsg); //通知房间内所有人
            noticeNoMove(); //通知双方都不能再行动了
            dispatchContinueButton(); //分配重开逻辑
        }
        else if (winMode == WinMode.WHITE_WIN) { //白方下赢
            noticeAllWinMsg(winMsg); //通知房间内所有人
            noticeNoMove(); //通知双方都不能再行动了
            dispatchContinueButton(); //分配重开逻辑
        }
        else if (winMode == WinMode.BLACK_ADMIT) { //黑方认输
            noticeAllWinMsg(winMsg); //通知房间内所有人
            noticeNoMove(); //通知双方都不能再行动了
            dispatchContinueButton(); //分配重开逻辑
        }
        else if (winMode == WinMode.WHITE_ADMIT) { //白方认输
            noticeAllWinMsg(winMsg); //通知房间内所有人
            noticeNoMove(); //通知双方都不能再行动了
            dispatchContinueButton(); //分配重开逻辑
        }
        else if (winMode == WinMode.BLACK_LEAVE) { //黑方逃离
            noticeAllWinMsg(winMsg); //通知房间内所有人
            noticeNoMove(); //通知双方都不能再行动了
            //此时不分配重开逻辑
        }
        else if (winMode == WinMode.WHITE_LEAVE) { //白方逃离
            noticeAllWinMsg(winMsg); //通知房间内所有人
            noticeNoMove(); //通知双方都不能再行动了
            //此时不分配重开逻辑
        }
        else if (winMode == WinMode.PERSONAL_RESTART) { //个人对弈模式的认输结束为直接重开
            noticeAllWinMsg(winMsg); //通知房间内所有人
            player_black.removeCustomBottom(player_black.findCustomBottomByTitle(this, "继续"));
            player_black.dispatchCustomBottom();
            reStartDelay(0); //直接重开
        }
        else { //玩家全部离开结束，直接清空界面
            overFlag = 0;//重置结束状态：对弈进行中未结束
            cur = 0;
            resetGrids();//如果黑白都为空，那么对局清空
        }
    }

    //给对弈双方分配再来一局逻辑
    protected void dispatchContinueButton() {
        if (player_black != null && player_white != null) {
            if (player_black == player_white) { //个人模式
                gameTip = "游戏结束！点击“继续”可再来一局。";
                player_black.removeCustomBottom(player_black.findCustomBottomByTitle(this, "继续"));
                player_black.registerCustomBottom(new CustomBottom(this, "继续", player -> {
                    player_black.removeCustomBottom(player_black.findCustomBottomByTitle(this, "继续"));
                    player_black.dispatchCustomBottom();
                    reStartDelay(0);
                }));
                player_black.dispatchCustomBottom();
            }
            else { //双人对弈
                gameTip = "游戏结束！双方点击“继续”可交换位置再来一局。";
                //黑方设置继续按钮
                player_black.removeCustomBottom(player_black.findCustomBottomByTitle(this, "继续"));
                player_black.registerCustomBottom(new CustomBottom(this, "继续", player -> {
                    if (overFlag == 2) {
                        overFlag = 3;
                        noticeAllBroadcast(player_black.getNickNameOrTemp() + "想要再来一局");
                    }
                    else {
                        player_white.removeCustomBottom(player_white.findCustomBottomByTitle(this, "继续"));
                        player_white.dispatchCustomBottom();
                        reStartDelay(0);
                    }
                    player_black.removeCustomBottom(player_black.findCustomBottomByTitle(this, "继续"));
                    player_black.dispatchCustomBottom();
                }));
                player_black.dispatchCustomBottom();
                //白方设置继续按钮
                player_white.removeCustomBottom(player_white.findCustomBottomByTitle(this, "继续"));
                player_white.registerCustomBottom(new CustomBottom(this, "继续", player -> {
                    if (overFlag == 2) {
                        overFlag = 4;
                        noticeAllBroadcast(player_white.getNickNameOrTemp() + "想要再来一局");
                    }
                    else {
                        player_black.removeCustomBottom(player_black.findCustomBottomByTitle(this, "继续"));
                        player_black.dispatchCustomBottom();
                        reStartDelay(0);
                    }
                    player_white.removeCustomBottom(player_white.findCustomBottomByTitle(this, "继续"));
                    player_white.dispatchCustomBottom();
                }));
                player_white.dispatchCustomBottom();
            }
        }
        else {
            if (player_black != null) {
                player_black.removeCustomBottom(player_black.findCustomBottomByTitle(this, "继续"));
                player_black.dispatchCustomBottom();
            }
            if (player_white != null) {
                player_white.removeCustomBottom(player_white.findCustomBottomByTitle(this, "继续"));
                player_white.dispatchCustomBottom();
            }
        }
    }

    //delay毫秒后重新开始对局
    public void reStartDelay(long delay) {
        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                Player temp = player_black;
                player_black = player_white;
                player_white = temp;

                cur = 1;
                resetGrids();

                noticeAllRefreshRoomInfo();
                DataManager.noticeAllRefreshRoomList();
                checkGameStart();
            }
        }, delay);
    }

    //通知胜负信息
    protected void noticeAllWinMsg(String winMsg) {
        //日志
        System.out.println(this.roomName + ":" + winMsg);
        if (player_black == player_white && player_black != null){
            player_black.sendMessage(Msger.winMessage, winMsg);
        }
        else {
            if (player_black != null) {
                player_black.sendMessage(Msger.winMessage, winMsg);
            }
            if (player_white != null) {
                player_white.sendMessage(Msger.winMessage, winMsg);
            }
        }
        for (Player player : spectatorList) {
            if (player != null) {
                player.sendMessage(Msger.winMessage, winMsg);
            }
        }
    }

    //通知房间里所有人更新游戏信息
    protected void noticeAllRefreshGameInfo() {
        String gameInfo = getGameInfo();
        if (player_black != null && player_white != null && player_black == player_white) {
            player_black.sendMessage(Msger.refreshGameInfo, gameInfo); //个人模式就只显示一次
        }
        else {
            if (player_black != null) {
                player_black.sendMessage(Msger.refreshGameInfo, gameInfo);
            }
            if (player_white != null) {
                player_white.sendMessage(Msger.refreshGameInfo, gameInfo);
            }
        }
        for (Player player : spectatorList) {
            player.sendMessage(Msger.refreshGameInfo, gameInfo);
        }
    }


    /**
     * 通知房间内所有人更新倒计时开始时间(单位：秒)（负数的话不显示倒计时）
     * 开启倒计时业务超时了就将超时的用户踢出去，代码调用玩家的离开方法
     */
    protected void noticeAllCountdown(int blackCountdown, int whiteCountdown) {
        /*把消息发送给客户端让客户端执行倒计时动画*/
        String countdownInfo = blackCountdown + "&" + whiteCountdown;
        if (player_black != null && player_white != null && player_black == player_white) {
            player_black.sendMessage(Msger.refreshCountdown, countdownInfo);
        }
        else {
            if (player_black != null) {
                player_black.sendMessage(Msger.refreshCountdown, countdownInfo);
            }
            if (player_white != null) {
                player_white.sendMessage(Msger.refreshCountdown, countdownInfo);
            }
        }
        for (Player player : spectatorList) {
            if (player != null) {
                player.sendMessage(Msger.refreshCountdown, countdownInfo);
            }
        }

        //开启倒计时业务超时了就将超时的用户踢出去，代码调用玩家的离开方法
        if (blackCountdownOverTimer != null) {
            blackCountdownOverTimer.cancel();
            blackCountdownOverTimer = null;
        }
        if (blackCountdown != -1) {
            blackCountdownOverTimer = new CountDownTimer(blackCountdown);
            blackCountdownOverTimer.schedule(new TimerTask() {
                @Override
                public void run() {
                    if (player_black != null) {
                        playerLeaveSeat(player_black);
                    }
                }
            }, blackCountdown * 1000L);
        }

        if (whiteCountdownOverTimer != null) {
            whiteCountdownOverTimer.cancel();
            whiteCountdownOverTimer = null;
        }
        if (whiteCountdown != -1) {
            whiteCountdownOverTimer = new CountDownTimer(whiteCountdown);
            whiteCountdownOverTimer.schedule(new TimerTask() {
                @Override
                public void run() {
                    if (player_white != null) {
                        playerLeaveSeat(player_white);
                    }
                }
            }, whiteCountdown * 1000L);
        }
    }

    /**
     * 通知指定用户刷新当前倒计时动画
     */
    protected void noticePlayerCurrentCountDown(Player player) {
        int blackRemainingSec = blackCountdownOverTimer != null ? blackCountdownOverTimer.getRemainingSec() : -1;
        int whiteRemainingSec = whiteCountdownOverTimer != null ? whiteCountdownOverTimer.getRemainingSec() : -1;
        player.sendMessage(Msger.refreshCountdown, blackRemainingSec + "&" + whiteRemainingSec);
    }

    /**
     * 对房间内所有人发送广播通知
     */
    protected void noticeAllBroadcast(String msg) {
        if (player_black != null && player_white != null && player_black == player_white) {
            player_black.sendMessage(Msger.broadcast, msg);
        }
        else {
            if (player_black != null) {
                player_black.sendMessage(Msger.broadcast, msg);
            }
            if (player_white != null) {
                player_white.sendMessage(Msger.broadcast, msg);
            }
        }
        for (Player player : spectatorList) {
            player.sendMessage(Msger.broadcast, msg);
        }
    }

    /**
     * 指定索引获取其在当前格子上的列
     */
    public int getColByIndex(int index) {
        return index % curGrids[0].length;
    }

    /**
     * 指定索引获取其在当前格子上的行
     */
    public int getRowByIndex(int index) {
        return index / curGrids[0].length;
    }

    //指定坐标获取该坐标在当前布局格子上的索引
    public int getIndex(int row, int col) {
        int width = curGrids[0].length;
        return row * width + col;
    }

    //获取坐标索引集合，参数为坐标索引
    public List<Integer> getIndexes(int... indexArr) {
        List<Integer> ids = new ArrayList<>();
        for (int i : indexArr) {
            ids.add(i);
        }
        return ids;
    }

    //根据坐标序列获取横纵坐标
    public int[] getPositionByIndex(int index) {
        int width = curGrids[0].length;
        int col = index % width;
        int row = index / width;
        return new int[]{row, col};
    }


    //准备开始记录历史
    protected void preHistory() {
        boolean isPersonal = (player_black == player_white); //是否个人模式
        boolean isAccountNormally = player_black.isAccountNormally() && player_white.isAccountNormally();
        if (isSaveHistoryToFile && isAccountNormally && !isPersonal) {
            history = new History();
            String dataTime = DateTimeUtil.localDateTimeToStr(LocalDateTime.now(), DateTimeUtil.ALL_FORMAT);
            String names = getBlackPlayerName() + "(黑)" + "-" + getWhitePlayerName() + "(白)";
            String gameInfo = "====开始====";
            String gridStr = GridsUtils.formatGrids(curGrids);
            history.add(dataTime + "\n" + names + "\n" + gameInfo + "\n" + gridStr + "\n");
            System.out.println(roomName + "准备开始记录战报：" + names);
        }
        else {
            history = null;
        }
    }


    //添加记录到历史
    protected void addHistory() {
        if (isSaveHistoryToFile && history != null) {
            String dataTime = DateTimeUtil.localDateTimeToStr(LocalDateTime.now(), DateTimeUtil.ALL_FORMAT);
            String names = getBlackPlayerName() + "(黑)" + "-" + getWhitePlayerName() + "(白)";
            String gameInfo = "第" + step + "步";
            String gridStr = GridsUtils.formatGrids(curGrids);
            history.add(dataTime + "\n" + names + "\n" + gameInfo + "\n" + gridStr + "\n");
        }
    }

    //添加记录到历史
    protected void addHistory(String content) {
        if (isSaveHistoryToFile && history != null) {
            String dataTime = DateTimeUtil.localDateTimeToStr(LocalDateTime.now(), DateTimeUtil.ALL_FORMAT);
            String names = getBlackPlayerName() + "(黑)" + "-" + getWhitePlayerName() + "(白)";
            String gameInfo = "第" + step + "步";
            String gridStr = GridsUtils.formatGrids(curGrids);
            history.add(dataTime + "\n" + names + "\n" + gameInfo + "\n" + content + "\n" + gridStr + "\n");
        }
    }

    //保存历史记录到本地文件 //最终文件格式形如  震棋_测试A(胜)_vs(35)_测试B(负)_20230928151246.txt
    protected void saveHistory(int winMode) {
        try {
            if (isSaveHistoryToFile) {
                String preFileName;
                if (winMode == WinMode.BOTH_DRAW) { //双方战平
                    preFileName = (roomName + "_" + player_black.getNickName() + WinTag.P + "_vs(" + step + ")_" + player_white.getNickName() + WinTag.P);
                }
                else if (winMode == WinMode.BLACK_WIN) { //黑方下赢
                    preFileName = (roomName + "_" + player_black.getNickName() + WinTag.S + "_vs(" + step + ")_" + player_white.getNickName() + WinTag.F);
                }
                else if (winMode == WinMode.WHITE_WIN) { //白方下赢
                    preFileName = (roomName + "_" + player_black.getNickName() + WinTag.F + "_vs(" + step + ")_" + player_white.getNickName() + WinTag.S);
                }
                else if (winMode == WinMode.BLACK_ADMIT) { //黑方认输
                    preFileName = (roomName + "_" + player_black.getNickName() + WinTag.R + "_vs(" + step + ")_" + player_white.getNickName() + WinTag.S);
                }
                else if (winMode == WinMode.WHITE_ADMIT) { //白方认输
                    preFileName = (roomName + "_" + player_black.getNickName() + WinTag.S + "_vs(" + step + ")_" + player_white.getNickName() + WinTag.R);
                }
                else if (winMode == WinMode.BLACK_LEAVE) {
                    preFileName = (roomName + "_" + leavePlayerRemember.getNickName() + WinTag.L + "_vs(" + step + ")_" + player_white.getNickName() + WinTag.S);
                }
                else if (winMode == WinMode.WHITE_LEAVE) {
                    preFileName = (roomName + "_" + player_black.getNickName() + WinTag.S + "_vs(" + step + ")_" + leavePlayerRemember.getNickName() + WinTag.L);
                }
                else {
                    System.out.println("战报未保存：winMode=" + winMode);
                    history = null;
                    return;
                }

                boolean isPersonal = (player_black == player_white); //是否个人模式
                if (history != null && !isPersonal) {
                    boolean saveSafeTime = history.checkSaveSafeTime(); //过滤速刷
                    if (saveSafeTime) {
                        boolean isDoStep = (step > 0); //进行过行动
                        if (isDoStep) {
                            history.add("----结束(共" + step + "回合)----");
                            String recordsPath = PathCost.RECORDS_DIR;
                            String curRoomRecordPath = recordsPath + this.getClass().getSimpleName();
                            FileUtils.createOrExistsDir(recordsPath);
                            FileUtils.createOrExistsDir(curRoomRecordPath);
                            String savedTimeStr = DateTimeUtil.localDateTimeToStr(LocalDateTime.now(), DateTimeUtil.ALLFORMAT);
                            String recordFileName = curRoomRecordPath + "/" + preFileName + "_" + savedTimeStr + ".txt";
                            FileUtils.createOrExistsFile(recordFileName);
                            boolean isSaveSuccess = FileUtils.writeFileFromString(recordFileName, history.toDataString());
                            System.out.println("保存战报：" + recordFileName + (isSaveSuccess ? "成功" : "失败"));
                            history = null;
                        }
                        else {
                            System.out.println("战报未保存：step=0");
                            history = null;
                        }
                    }
                    else {
                        System.out.println("战报未保存：saveSafeTime=" + saveSafeTime);
                        history = null;
                    }
                }
                else {
                    System.out.println("战报未保存：history = null");
                    history = null;
                }
            }
            else {
                history = null;
            }
        }
        catch (Exception e) {
            System.out.println("保存战报发生异常：" + e);
        }
    }

    /**
     * 记录对弈信息 (只有双人完整的对弈才会记录，必须下完，认输都不记录)
     */
    public void savePlayingInfo(int winMode) {
        try {
            boolean isPersonal = (player_black == player_white); //是否个人模式
            if (checkSaveSafeTime() && !isPersonal) { //非速刷且非个人模式时才记录
                if (winMode == WinMode.BOTH_DRAW) { //双方战平
                    player_black.updatePlayingInfo(this, player_white,false);
                    player_white.updatePlayingInfo(this, player_black,false);
                }
                else if (winMode == WinMode.BLACK_WIN) { //黑方下赢
                    player_black.updatePlayingInfo(this, player_white,true);
                    player_white.updatePlayingInfo(this, player_black,false);
                }
                else if (winMode == WinMode.WHITE_WIN) { //白方下赢
                    player_black.updatePlayingInfo(this, player_white,false);
                    player_white.updatePlayingInfo(this, player_black,true);
                }
                else if (winMode == WinMode.BLACK_ADMIT) { //黑方认输
                    player_black.updatePlayingInfo(this, player_white,false);
                    player_white.updatePlayingInfo(this, player_black,true);
                }
                else if (winMode == WinMode.WHITE_ADMIT) { //白方认输
                    player_black.updatePlayingInfo(this, player_white,true);
                    player_white.updatePlayingInfo(this, player_black,false);
                }
                else if (winMode == WinMode.BLACK_LEAVE) { //黑方逃离
                    leavePlayerRemember.updatePlayingInfo(this, player_white,false);
                    player_white.updatePlayingInfo(this, leavePlayerRemember,true);
                }
                else if (winMode == WinMode.WHITE_LEAVE) { //白方逃离
                    player_black.updatePlayingInfo(this, leavePlayerRemember,true);
                    leavePlayerRemember.updatePlayingInfo(this, player_black,false);
                }
            }
        }
        catch (Exception e) {
            System.out.println("记录玩家对弈信息发生异常：" + e);
        }
    }

    /**
     * 校验是否速刷
     * (从开始记录历史到保存到本地文件必须超过一定时间)
     */
    public boolean checkSaveSafeTime() {
        long curTime = System.currentTimeMillis();
        return curTime - playingStartTime >= Long.parseLong(AppConfig.playing_save_safe_time);
    }

    /**
     * 设置该房间当前模式
     * 必须是在构造函数参数modes里配置过的，否则都会变成“默认”模式
     */
    public final void setCurRoomMode(String curMode) {
        this.mode.setCurMode(curMode);
        this.onModeChanged(curMode);
    }

    /**
     * 该房间当前的模式和传入的模式是否为同一模式？
     * 没配置过的模式始终返回false
     */
    public final boolean isCurMode(String mode) {
        return this.mode.isCurMode(mode);
    }

    /**
     * 当前模式是否为默认模式
     */
    public final boolean isNormalMode() {
        return this.isCurMode("默认");
    }

    /**
     * 获取当前模式
     */
    public final String getCurMode() {
        return this.mode.getCurMode();
    }

    /**
     * 获取所有模式，始终至少会有一个”默认“模式的
     */
    public final String getAllModes() {
        String[] modes = this.mode.getAllModes();
        return String.join(",", modes);
    }

    /**
     * 模式切换时调用
     */
    protected void onModeChanged(String mode) {

    }


    /**
     * 获取排序权重值 (客户端据此排序，越大越在上方)
     */
    public int getSortingValue() {
        return sortingWeighter.getSortingWeightValue();
    }

    /**
     * 设置房间排序是否沉底
     */
    public void setSortingBottomValue(boolean isPlacedTop) {
        sortingWeighter.setSortingBottom(isPlacedTop);
    }

    /**
     * 获取房间排序权重
     */
    public int getSorterOrder() {
        return sortingWeighter.order;
    }

    /**
     * 设置默认房间排序权重 （注意模式设置成多少就一定排多少）
     */
    public void setSorterOrder(int order) {
        sortingWeighter.order = order;
    }

    /**
     * 获取预设消息列表
     */
    public String getDefMessageList() {
        if (!isForbidChat && ChatData.PRE_CHAT_MESSAGE_LIST.size() > 0) {
            return String.join(";", ChatData.PRE_CHAT_MESSAGE_LIST);
        }
        return "-1";
    }

    /**
     * 根据序号获取预设消息
     */
    public String getDefMessage(int no) {
        if (!isForbidChat && no < ChatData.PRE_CHAT_MESSAGE_LIST.size()) {
            return ChatData.PRE_CHAT_MESSAGE_LIST.get(no);
        }
        return null;
    }

    /**
     * 给房间内所有用户发送聊天消息
     */
    public void sendAllChatMessage(Player source,String message) {
        if (isForbidChat) return;
        String chatCount = getAndIncrement();
        String dataStr = chatCount + "&" + source.getNickNameOrTemp() + "&" + message;
        if (player_black != null && player_white != null && player_black == player_white) {
            player_black.sendMessage(Msger.pushChatMessage, dataStr);
        }
        else {
            if (player_black != null) {
                player_black.sendMessage(Msger.pushChatMessage, dataStr);
            }
            if (player_white != null) {
                player_white.sendMessage(Msger.pushChatMessage, dataStr);
            }
        }
        for (Player player : spectatorList) {
            player.sendMessage(Msger.pushChatMessage, dataStr);
        }
    }

    /**
     * 获取和更新聊天计数
     */
    public synchronized String getAndIncrement() {
        if (chatCount == 999){
            chatCount = 0;
        }
        else {
            chatCount++;
        }
        return String.format("%03d", chatCount); //转换成 三位数 字符串
    }

    /**
     * 判断该房间是否允许聊天
     */
    public boolean isForbidChat() {
        return isForbidChat;
    }

    /**
     * 设置该房间是否允禁止聊天
     */
    public void setForbidChat(boolean forbidChat) {
        isForbidChat = forbidChat;
    }

    /**
     * 排序权重类
     */
    public static class SortingWeighter {

        private Room room; //为了获取在线认输和对弈热度
        private int order; //保留房间创建时候的排序
        private boolean isPlacedTop = false; //是否置顶
        private boolean isPlacedBottom = false; //是否置底

        private static volatile int count = 0;

        public synchronized int getAndIncrement() {
            return count++;
        }

        public SortingWeighter(Room room) {
            this.order = getAndIncrement();
            this.room = room;
        }

        public int getSortingWeightValue() {
            //++暂时优先根据在线人数排序，再根据创建时排序。置底的永远再最下方。以后会设计更加复杂的排序方式
            int b = count - order;
            int p = room.getPlayerNum() * count;
            if (isPlacedBottom) {
                return -1;
            }
            return b + p;
        }

        public void setSortingBottom(boolean isPlacedBottom) {
            this.isPlacedBottom = isPlacedBottom;
        }
    }
}
